#!/usr/bin/env python
#     Copyright 2024, Kay Hayen, mailto:kay.hayen@gmail.com find license text at end of file


""" Runner for generated tests of Nuitka.

These tests are created on the fly, and some use Nuitka internals to
decide what to test for or how, e.g. to check that a type indeed does
have a certain slot.

"""

import os
import sys

# Find nuitka package relative to us.
sys.path.insert(
    0,
    os.path.normpath(
        os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..")
    ),
)

# isort:start

import nuitka.specs.BuiltinStrOperationSpecs
from nuitka.tools.specialize.SpecializePython import (
    python2_dict_methods as dict_method_names,
)
from nuitka.tools.specialize.SpecializePython import (
    python2_str_methods as str_method_names,
)
from nuitka.tools.specialize.SpecializePython import (
    python3_bytes_methods as bytes_method_names,
)
from nuitka.tools.testing.Common import (
    compareWithCPython,
    createSearchMode,
    my_print,
    scanDirectoryForTestCases,
    setup,
)

# For templating.
operations = (
    ("Add", "+"),
    ("Sub", "-"),
    ("Pow", "**"),
    ("Mult", "*"),
    ("FloorDiv", "//"),
    ("Div", "/"),
    ("Mod", "%"),
    ("Pow", "**"),
    ("LShift", "<<"),
    ("RShift", ">>"),
    ("BitAnd", "&"),
    ("BitOr", "|"),
    ("BitXor", "^"),
    ("Divmod", "divmod"),
    ("Subscript", "["),
)


# For typical constant values to use in operation tests.
candidates = (
    ("NoneType", "None", "None"),
    ("bool", "True", "False"),
    ("int", "17", "-9"),
    ("float", "17.2", "-8"),
    ("complex", "2j", "-4j"),
    ("str", "'lala'", "'lol'"),
    ("bytearray", "bytearray(b'lulu')", "bytearray(b'lol')"),
    ("list", "[1,2]", "[3]"),
    ("tuple", "(1,2)", "(3,)"),
    ("set", "set([1,2])", "set([3])"),
    ("frozenset", "frozenset([1,2])", "frozenset([3])"),
    ("dict", "{1:2}", "{3:4}"),
)


# For making an operator usage, needed because divmod is function style.
def makeOperatorUsage(operator, left, right):
    if operator == "divmod":
        return "divmod(%s, %s)" % (left, right)
    elif operator == "[":
        return "%s[%s]" % (left, right)
    else:
        return "%s %s %s" % (left, operator, right)


def main():
    python_version = setup(suite="generated", needs_io_encoding=True)

    search_mode = createSearchMode()

    # Singleton, pylint: disable=global-statement
    global operations
    global candidates

    if python_version >= (3, 5):
        operations += (("MatMult", "@"),)

    if python_version < (3,):
        candidates += (("long", "17L", "-9L"),)
        candidates += (("unicode", "u'lala'", "u'lol'"),)
    else:
        candidates += (("bytes", "b'lala'", "b'lol'"),)

    method_arguments = {}

    for str_method_name in str_method_names:
        spec = getattr(
            nuitka.specs.BuiltinStrOperationSpecs, "str_%s_spec" % str_method_name, None
        )

        if spec is None:
            my_print(
                "Warning, str function '%s' has no spec." % str_method_name,
                style="yellow",
            )
            continue

        method_arguments[str_method_name] = spec.getArgumentNames()

    for bytes_method_name in bytes_method_names:
        spec = getattr(
            nuitka.specs.BuiltinBytesOperationSpecs,
            "bytes_%s_spec" % bytes_method_name,
            None,
        )

        if spec is None:
            my_print(
                "Warning, bytes function '%s' has no spec." % bytes_method_name,
                style="yellow",
            )
            continue

        method_arguments[bytes_method_name] = spec.getArgumentNames()

    for dict_method_name in dict_method_names:
        spec = getattr(
            nuitka.specs.BuiltinDictOperationSpecs,
            "dict_%s_spec" % dict_method_name,
            None,
        )

        if spec is None:
            my_print(
                "Warning, dict function '%s' has no spec." % dict_method_name,
                style="yellow",
            )
            continue

        method_arguments[dict_method_name] = spec.getArgumentNames()

    template_context = {
        "operations": operations,
        "dict_method_names": [
            dict_method_name
            for dict_method_name in dict_method_names
            if dict_method_name in method_arguments
        ],
        "str_method_names": [
            str_method_name
            for str_method_name in str_method_names
            if str_method_name in method_arguments
        ],
        "bytes_method_names": [
            bytes_method_name
            for bytes_method_name in bytes_method_names
            if bytes_method_name in method_arguments
        ],
        "method_arguments": method_arguments,
        "inplace_operations": tuple(
            operation
            for operation in operations
            if operation[0] not in ("Divmod", "Subscript")
        ),
        "candidates": candidates,
        "makeOperatorUsage": makeOperatorUsage,
        "len": len,
    }

    # Now run all the tests in this directory.
    for filename in scanDirectoryForTestCases(".", template_context=template_context):
        extra_flags = [
            # No error exits normally, unless we break tests, and that we would
            # like to know.
            "expect_success",
            # Keep no temporary files.
            "remove_output",
            # Do not follow imports.
            "--nofollow-imports",
            # Use the original __file__ value, at least one case warns about things
            # with filename included.
            "--file-reference-choice=original",
            # Cache the CPython results for reuse, they will normally not change.
            "cpython_cache",
        ]

        # This test should be run with the debug Python, and makes outputs to

        active = search_mode.consider(dirname=None, filename=filename)

        if active:
            compareWithCPython(
                dirname=None,
                filename=filename,
                extra_flags=extra_flags,
                search_mode=search_mode,
            )

    search_mode.finish()


if __name__ == "__main__":
    main()

#     Python test originally created or extracted from other peoples work. The
#     parts from me are licensed as below. It is at least Free Software where
#     it's copied from other people. In these cases, that will normally be
#     indicated.
#
#     Licensed under the Apache License, Version 2.0 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.
